Глава 10

ВАРИАНТЫ

10.1. ОСНОВНЫЕ СВОЙСТВА ВАРИАНТА

Вариант (в Delphi 1 он отсутствует) - это тип variant, разработанный специально для тех случаев, когда на этапе компиляции программист не может сказать, какого типа данные будут использоваться в выражении или как параметры вызова подпрограмм. Переменная-вариант занимает в памяти дополнительные 2 байта, в которые помещается информация о действительном типе переменной. Эта информация позволяет компилятору создать код, который будет осуществлять необходимое преобразование типов на этапе прогона программы.

В переменную-вариант можно поместить:

Варианты могут участвовать в целочисленных, вещественных, логических и время-дата выражениях при условии корректности соответствующих преобразований. Например, если варианту v присвоена строка '1.0', то выражение 1+v будет правильным вещественным значением 2,0. Однако если v := 'текст', выражение 1+v вызовет исключение EVariantError.

В Delphi определены такие константы, указывающие тип помещенных в вариант данных:

Таблица 10.1. Типы возможных значений варианта

Имя

Константа

Смысл

varEmp.ty

$0000

Нет данных

varNull

$0001

Неизвестный тип параметра

varSmallInt

$0002

Целый тип Smallint

varlnteger

$0003

Целый тип Integer

varSingle

$0004

Вещественный тип Single

varDouble

$0005

Вещественный тип Double

varCurrency

$0006

Вещественный тип Currency

varDate

$0007

Тип дата-время

varOleStr

$0008

OLE-строка в кодировке Unicode

varDispatch

$0009

Указатель на OLE-объект

varError

$000А

Код ошибки

varBoolean

$000В

Тип WordBool

varVariant

$000С

Тип Variant (только для вариантных массивов)

varUnknow

$0011

Неизвестный OLE-объект

varByte

$0100

Целый тип Byte

varString

$0100

Строковый тип

varArray

$2000

Вариантный массив

varByRef

$4000

Указатель на данные

Структура вариантного типа описывается следующим образом:

TVarData = packed record

VType: Word;

Reservedly Reserved2, ReservedS: Word;

case Integer of

varSmallInt: (VSmallInt: Smallint);

varlnteger: (VInteger: Integer);

varSingle: (VSingle: Single);

varDouble: (VDouble: Double);

varCurrency: (VCurrency: Currency);

varDate:(VDate: Double) ;

varOleStr: (VOleStr: PWideChar) ;

varDispatch: (VDispatch: Pointer);

varError: (VError: WordBool);

varString: (VString: Pointer);

varArray: (VArray: PVarArray) ;

varByRef: (VPointer: Pointer);

end;

Как нетрудно убедиться, любая переменная вариантного типа представляет собой 16-байтную запись, содержащую 8-байтную вариантную часть, которая хранит либо собственно данные, либо их адрес (т. е. указатель на динамически размещ

К чему приводится

Тип данных в варианте

varEmpty

Целые

Вещественные

Дата-

Время

Строковые

Логические

К дата- 'У: время

30.12.

1899 00:00:00

Преобразование в

Double

Преобразование в

Double

Без преобразования Преобразование в дату

Преобразование в Double

К целым

0

Преобразование в соответствующий тип

Округление

до ближайшего целого

Округление до ближайшего целого

Преобразование в целый тип

0 для False,
иначе-1 (255 для

Byte)

.'К дата- 'У: время

30.12.1899 00:00:00

Преобразование в

Double

Преобразование в

Double

Без преобразования

Преобразование в дату

Преобразование в

Double

К строковым

Пустая строка

Преобразование в символьный вид

Преобразование в символьный вид

Преобразование в символьный вид

Без преобразования

'0'для False,'-!' для True

К логическим

False

False для 0, иначе

True

False для 0, иначе

True

False для 0, иначе

True

False для 'False' и для '0', иначе True

Без преобразования

енные данные). В поле VType в момент создания варианта компилятор помещает, признак отсутствия данных varEmpty. В работающей программе значение этого поля меняется в соответствии с текущим типом данных, размещенных в вариантной части. Замечу, что программа не может получить прямого доступа к полям вариантной записи. Получить тип вариантных данных можно с помощью функции varType (см. ниже), а изменить тип - путем присваивания варианту нового значения.

10.2. ПРЕОБРАЗОВАНИЕ ВАРИАНТОВ К ДАННЫМ ДРУГИХ ТИПОВ

При участии вариантов в выражениях, а также при присваивании их значений переменным других типов тип размещенных в варианте данных преобразуется по следующим правилам:

Таблица 10.2. Преобразование типов для вариантов

Здесь

К целым Отнесены varByte, varSmallInt, varlnteger/ varError;

К вещественным — varSingle, varDouble/ varCurrency;

К строковым -var String, varOleStr.

10.3. ПОДПРОГРАММЫ ДЛЯ РАБОТЫ С ВАРИАНТАМИ

Для работы с вариантами можно использовать такие подпрограммы:

Таблица 10.3. Подпрограммы для работы с вариантами

function VarAsType(const V: Variant; VarType: Integer): Variant;

Преобразует данные варианта V к типу, определяемому параметром VarType

procedure VarCast(var Dest: Variant; const Source: Variant; Var

Type: Integer) ;

Преобразует данные варианта Source к типу,определяемому параметром VarType, и помещает результат в переменную Dest

procedure VarClear(var V: Variant) ;

 

Освобождает динамическую память, если она была связана с вариантом, и дает ему тип varEmpty

procedure VarCopy(var Dest: Variant; const Source: Variants;

Копирует параметр Source в вариант Dest

function VarFrom-DateTime(DateTime: TDateTime):Variant;

Возвращает вариант, содержащий данные DateTime типа дата-время

function VarIsEmpty(const V:

Variant): Boolean;

Возвращает True, если вариант V не содержит данных

function VarIsNull(const V: Vari

ant) : Boolean;

Возвращает True, если вариант V содержит данные неопределенного типа (varNull) ',

function VarToDateTime(const V:

Variant): TDateTime) ;

Преобразует данные варианта V к типу дата-время

function VarToStr(const V: Vari ant) : String;

Преобразует данные варианта V к строке ;

function VarType(const V: Variant) : Integer;

Возвращает тип хранящихся в варианте данных i

 

10.4. ВАРИАНТНЫЕ МАССИВЫ

Значением варианта может быть массив данных, такие варианты называются вариантными массивами. (Не путайте с обычным или динамическим массивом, элементами которого являются варианты!) Значениями элементов вариантного массива могут быть любые допустимые для варианта значения, кроме строк varstring. Значениями элементов вариантного массива могут быть и варианты, а это значит, что в таком массиве могут одновременно храниться данные разных типов (и в том числе строки). Например:

var

V: Variant;

begin

// Создаем одномерный вариантный массив с 5 элементами:

V := VarArrayCreate([0, 4], varVariant);

// Наполняем его:

V[0] := 1; //Тип целый

V[1] := 1234.5678; //Тип вещественный

V[2] := 'Hello world'; //Строковый тип

V[3] := True; //Логический тип

//Пятым элементом исходного массива сделаем еще один массив:

V[4] := VarArrayOf([l, 10, 100, 1000]);

Caption := V[2]; //Hello world

IbOutput.Caption := IntToStr(V[4][2]); //200

end;

Все действия с вариантными массивами осуществляются с помощью следующих процедур и функций:

Таблица 10.4. Подпрограммы для работы с вариантными массивами

function VarArrayCreate(const

Bounds: array of Integer; VarType: Integer): Variant;

Создает вариантный массив из элементов типа VarType с количеством и границами измерений, указываемых параметром Bounds

function VarArrayDimCount(const

A: Variant): Integers;

Возвращает количество измерений вариантного массива А или 0, если А не массив

function VarArrayHighBound(const

A: Variant; Dim: Integer): Integer;

Возвращает верхнюю границу индекса вариантного массива А по измерению Dim

function VarArrayLock(var A:

Variant): Pointer;

 

Блокирует массив (предотвращает его возможные изменения размеров) и возвращает указатель на связанные с ним данные

function VarArrayLowBound(const

A: Variant; Dim: Integer): Integers;

Возвращает нижнюю границу индекса вариантного массива А по измерению Dim

function VarArrayOf(const Values:

array of Variant): Variants;

 

 

Создает одномерный вариантный массив по перечню значений, содержащихся в открытом массиве Values. Нижняя граница индексов вариантного массива в этом случае равна 0

procedure VarArrayRedim(var A:

Variant; HighBound: Integer) ;

 

 

 

 

Изменяет верхнюю границу индекса вариантного

массива А на величину HighBound. Вызов про

цедуры игнорируется, если массив был заблоки

рован функцией VarArrayLock

function VarArrayRef(const A:

Variant): Variants;

Возвращает ссылку на вариантный массив. Ис

пользуется при обращении к API-функциям

procedure VarArrayUnlock(var A:

Variant)

Отменяет действие функции VarArrayLock

 

10.5. ПОЛЬЗОВАТЕЛЬСКИЕ ВАРИАНТЫ

Стандартный вариант может хранить только одно из значений, указанных в табл. 10.2. В версии Delphi 6 появились так называемые пользовательские варианты, которые фактически снимают ограничения на характер значений варианта.

Чтобы познакомиться со свойствами новых вариантов, воспользуемся одним из них - вариантом, способным хранить комплексные числа, преобразовывать их в другие типы и осуществлять над ними нужные действия. Как мы увидим дальше, создание пользовательского варианта может быть весьма трудоемким делом - все зависит от сложности хранимых в нем данных. Мы воспользуемся вариантом, созданным разработчиками Delphi и включенным в модуль

VarCmplx.

Создайте такой обработчик bbRunClick:

uses VarCmplx; // Эта ссылка обязательна!

procedure TfmExample.bbRunClick(Sender: TObject);

var

VI, V2: Variants-begin

// Создаем два случайных комплексных числа:

VI := VarComplexCreate(Trunc(Random*1000)/100,

Trunc(Random*1000)/100) ;

V2 := VarComplexCreate(Trunc(Random*1000)/100,

Trunc(Random*1000)/100) ;

with mmOutput.Lines do

begin

// Пустая строка-разделитель

Add ( ' ' ) ;

Add('1-e число: '# 9+V1) ;

Add('2-е число: '#9+V2);

Add('Сложение'#9+(V1+V2));

Add('Вычитание'#9+(V1-V2));

Add('Умножение'# 9+(VI*V2)) ;

Add('Деление'#9#9+(V1/V2))

end

end;

Небольшой комментарий: сложная конструкция Trunc (Random*1000) /100 понадобилась только для того, чтобы реальные и мнимые части комплексных чисел содержали по три значащих цифры.

Вид экрана работающей программы показан на рис. 10.1. Как видим, новый вариант легко справляется с поддержкой комплексных чисел: функция VarComplexCreate создает вариант, содержащий комплексное число, а дальнейшее поведение варианта -стандартное (он поддерживает математические операции и преобразование к строковому типу). Однако эта легкость обманчива: исходный текст модуля VarCmplx, который, собственно, и придал варианту дополнительные свойства (по умолчанию располагается в файле Source\Rtl\Common\VarCmplx.pas), содержит более 30000 байт..

На с. 229 показана структура записи TVarData. Два первых байта в этой записи (поле VType) хранят признак значения варианта, остальные 14 могут использоваться для размещения данных.

Рис. 10.1. Демонстрация комплексных вариантов

Создание пользовательского варианта проходит в три этапа.

  1. Сначала в записи rvarData размещаются новые данные или ссылка на них.
  2. Объявляется класс, который является прямым или косвеннымпотомком специального класса TCustomVariantType. В этомклассе предусматриваются все необходимые методы для реализации свойств варианта: присваивания ему новых значений, преобразования хранящихся значений к другим типам, выполнения необходимых математических действий.
  3. Создаются вспомогательные методы для объявления потомков нового класса и определения их типов.

В результате перечисленных шагов вы получаете полноценный вариант, обогащенный новыми свойствами: он может хранить не только те значения, которые перечислены в табл. 10.2, но и любые другие, в том числе свойства и методы! (В этом последнем случае наследником для исполняемого класса нового варианта вместо TCustomVariantType является TInvokeableVariantType или TPublishableVariantType.)

10.5.1. Размещение в варианте новых значений

Для размещения в варианте нового (не предусмотренного стандартным вариантом) значения нужно создать соответствующий класс и поместить в подходящее поле rvarData объект этого класса. Вот как, например, размещаются комплексные данные в модуле VarCmplx:

TComplexVarData = packed record

VType: TVarType;

Reserved1, Reserved2, Reserved3: Word;

VComplex: TComplexData;

Reserved4: Longint;

end;

Такая запись лишь сохраняет 16-байтную структуру TVarData, помещая в поле VComplex ссылку на объект класса TComplexData. Собственно комплексные числа хранятся в полях достаточно сложного класса:

type

TComplexData = class(TPersistent) private

FReal, FImaginary: Double;

end;

В этом классе предусмотрены многочисленные методы, управляющие новыми данными. Так, простой вызов VarComplexCreate

приводит к срабатыванию нескольких методов, создающих объект VComplex и наполняющих его поля:

procedure VarComplexCreateInto(var ADest: Variant;

const AComplex: TComplexData);

begin

VarClear(ADest);

TComplexVarData(ADest).VType := VarComplex;

TComplexVarData(ADest).VComplex := AComplex;

end; function VarComplexCreate(const AReal, AImaginary: Double):

Variant;

begin

VarComplexCreateInto(Result,

TComplexData.Create(AReal, AImaginary)) ;

end;

(CM. файл Source\Rtl\Common\VarCmplx.pas).

Запись в которой размещаются новые данные или ссылка на поддерживающий их обьект, должно обьявляться как packed record.

10.5.2. Создание наследника TCustomVariantType

Тип TCustomVariantType или его ближайшие Наследники TPublishableVariantType и TInvokeableVariantType Содержат методы и

свойства, которые в нужный момент вызывают методы и свойства объекта VComplex для осуществления тех или иных преобразований. В модуле varcmpix объявляется такой класс:

type

TComplexVariantType =

class(TPublishableVariantType, IVarStreamable) protected

function LeftPromotion(const V: TVarData;

const Operator: TVarOp;

out RequiredVarType: TVarType): Boolean; override;

function RightPromotion(const V: TVarData;

const Operator: TVarOp;

out RequiredVarType: TVarType): Boolean; override;

function Getlnstance(const V: TVarData): TObject; override;

public

procedure Clear(var V: TVarData);

override;

function IsClear(const V: TVarData): Boolean; override;

procedure Copy(var Dest: TVarData;

const Source: TVarData;

const Indirect: Boolean);

override;

procedure Cast(var Dest:

TVarData;

const Source: TVarData);

override;

procedure CastTo(var Dest: TVarData;

const Source: TVarData;

const AVarType: TVarType);

override;

procedure BinaryOp(var Left: TVarData;

const Right: TVarData;

const Operator: TVarOp); override;

procedure UnaryOp(var Right: TVarData;

const Operator: TVarOp);

override;

function CompareOp(const Left: TVarData;

const Right: TVarData;

const Operator: Integer): Boolean;

override;

procedure Streamin(var Dest: TVarData;

const Stream: TStream) ;

procedure StreamOut(const Source: TVarData;

const Stream: TStream) ;

end;

Обратите внимание: класс TComplexVariantType - интерфейсный (см. п. 9.4.1). Помимо общих для варианта методов он реализует также два метода, специфичных для интерфейса Ivarstreamabie -Streamin и StreamOut, с помощью которых значения нового интерфейса сохраняются в потоке и считываются из него.

Задача этого класса - дать единообразные команды, способные интерпретироваться объектом vcomplex как команды преобразования типа хранящихся данных, их сравнения, реализации над ними тех или иных операций, наконец, записи их в поток и чтения из него. Например, метод cast этого класса вызывается для преобразования других типов значений к комплексному типу, метод castTo - для обратного преобразования, метод BinaryOp реализует бинарную операцию, a Unarydp - унарную и т. д.

Еще раз подчеркну, что основная работа (например, по выполнению бинарных операций) реализуется методами класса TComplex-Data. Класс TCompiexVariantType перекрывает абстрактные методы своего родителя, подключая TComplexData к решению той или иной проблемы.

Поскольку для создания экземпляра нового варианта необходим уже готовый экземпляр (объект) класса TCompiexVariantType, он создается в секции инициализации модуля varcmpix и уничтожается в завершающей секции:

initialization

ComplexVariantType := TCompiexVariantType.Create;

finalization

FreeAndNil(ComplexVariantType);

10.5.3. Создание вспомогательных методов

Несмотря на интенсивное использование классов TCompiexData и TCompiexVariantType, эти классы в конечном счете остаются скрытыми от пользователя нового варианта за счет набора вспомогательных

методов, таких как VarComplexCreate, VarIsComplex, VarAsComplex и т.

п., которые преобразуют обычные процедурные вызовы в вызовы методов и обращения к свойствам соответствующих классов.